/* * Copyright (c) 2009 Dukascopy (Suisse) SA. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 * 
 * -Redistribution in binary form must reproduce the above copyright notice, 
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 * 
 * Neither the name of Dukascopy (Suisse) SA or the names of contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL 
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. DUKASCOPY (SUISSE) SA ("DUKASCOPY")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL DUKASCOPY OR ITS LICENSORS BE LIABLE FOR ANY LOST 
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, 
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY 
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, 
 * EVEN IF DUKASCOPY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 */
package richpumpdike;


import com.dukascopy.api.*;
import com.dukascopy.api.IEngine.OrderCommand;
import com.dukascopy.api.IMessage.Type;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

public class RichPumpDike implements IStrategy {
   private IEngine engine;
    private IConsole console;
    private IContext context;
    private IAccount account;    
    private SimpleDateFormat sdf;        

    @Configurable("Currency")
    public Instrument currencyInstrument = Instrument.EURUSD;
//    @Configurable("Start rate")
//    public double startRate =1.31 ;
    //mode 0 - no stop loss is set. Parameters stoploss are showed but not used
    //mode 1 - stop loss set in pipes. Parameters stoploss should be set to pipes numeb (example: 30)
    //mode 2 - stop loss set for absolute level. Parameters stoploss should be set to absolute level (example: 1.375). If one of the parameters is reached, statedy will stop
    @Configurable("Mode 0 - no Stop Loss, 1 - in pipes, 2 - absolute level")
    public int mode = 2;
    @Configurable("Amount")
    public double amount = 0.001;
    @Configurable("Take profit BUY")
    public int takeProfitBuy    = 30;
    @Configurable("Take profit SELL")
    public int takeProfitSell    = 30;
    @Configurable("Stop loss BUY")
    public double stopLossBuy    = 1.2;
    //V Modu 2 musi byt stopLossBuy mensi nez stopLossSell protoze se jedna o hladinu, ktera kdyz je dosazena tak se strategie zastavi
    @Configurable("Stop loss SELL")
    public double stopLossSell    = 1.342;
    @Configurable("Slippage")
    public double slippage = 5;    
    
    //Set how far it will be searching for match in method countainsLevel. Is In pipes, will be searching in +- level width. Is reconmended to be half of the take profit
    @Configurable("Level width")
    public int levelWidth = 15;
    @Configurable("Trade start hour GMT")
    public String startTime = "06:00";
    @Configurable("Trade close hour GMT")
    public String endTime = "22:00";
    //@Configurable("Time to waint untill the order is filled")
    public int MAX_ORDER_WAIT = 2000;
    
    private boolean strategyRunning = true;
    private double amountDelta = 0;
    private int counter = 0;    

    private double takeProfitPriceBuy;
    private double takeProfitPriceSell;    
    private String parsedStartTime;
    private String parsedEndTime;

    public void onBar(Instrument instrument, Period period, IBar askBar, IBar bidBar) throws JFException {
    }

    public void onStart(IContext context) throws JFException {
        this.engine = context.getEngine();
        this.console = context.getConsole();
        this.context = context;
        amountDelta = amount;
        sdf = new SimpleDateFormat("HH:mm");
        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
        parsedStartTime = startTime.replace(":", "");
        parsedEndTime = endTime.replace(":", "");        
    }

    public void onAccount(IAccount account) throws JFException {
        this.account = account;
    }

    public void onMessage(IMessage message) throws JFException {
      if (message.getType() == Type.ORDER_CLOSE_OK && strategyRunning)      //If is is closed message
        if (message.getOrder().getProfitLossInPips() > 0)    //If this is a profit close (NOT STOP LOSS)
        {        
          double roundedClosePrice = roundDecimal(message.getOrder().getClosePrice());

          if (!message.getOrder().isLong()){                                                            //If closed order was SELL                                                  
            if (!containsLevel(roundedClosePrice, IEngine.OrderCommand.BUY))                           //check if on the specific level already exist a sell. Level is current value +- slippage. It is because of small diferences (like 1 or 2 pipes)
              createOrders(message.getOrder().getClosePrice(), currencyInstrument, OrderCommand.BUY);                      //make new buy and wait till it is realized                                                                          
            if (!containsLevel(roundedClosePrice, IEngine.OrderCommand.SELL))                           //check if on the specific level already exist a sell. Level is current value +- slippage. It is because of small diferences (like 1 or 2 pipes)
              createOrders(message.getOrder().getClosePrice(), currencyInstrument, OrderCommand.SELL);
          } 
          else if (message.getOrder().isLong()) {                                                       //If closed order was BUY                                  
            if (!containsLevel(roundedClosePrice, IEngine.OrderCommand.SELL))                            //pokud na dane hladine neni sell          
              createOrders(message.getOrder().getClosePrice(), currencyInstrument, OrderCommand.SELL);                                        
            if (!containsLevel(roundedClosePrice, IEngine.OrderCommand.BUY))                            //pokud na dane hladine neni sell          
              createOrders(message.getOrder().getClosePrice(), currencyInstrument, OrderCommand.BUY);                      
          }
        } else if (mode == 2 && strategyRunning) {                                                                         //Else this was closed on stop loss. If it is the mode 2. It means the end of the strategy            
          onStop();
        }
    }

    public void onStop() throws JFException {
        strategyRunning = false;
        print("Stop stragegy");
        for (IOrder order : engine.getOrders()) {
            order.close();
        }
    }

    public void onTick(Instrument instrument, ITick tick) throws JFException {
        if (instrument.equals(currencyInstrument) && isValidTime(tick.getTime())  && strategyRunning) {
            monitorOrder(instrument, tick);
        }
    }

    private void monitorOrder(Instrument instrument, ITick tick) throws JFException {        
        // Create first order
        if (engine.getOrders().size() < 1  && strategyRunning) {
            takeProfitPriceBuy = instrument.getPipValue() * takeProfitBuy;
            takeProfitPriceSell = instrument.getPipValue() * takeProfitSell;
            createFirstOrders(tick, instrument, amountDelta);                
        }         
    }
    

   private void createFirstOrders(ITick tick, Instrument instrument, double orderAmount ) throws JFException {
       createOrders(tick.getAsk(), instrument, IEngine.OrderCommand.BUY);        
       createOrders(tick.getBid(), instrument, IEngine.OrderCommand.SELL);
   }
   
    
    private void createOrders(double price, Instrument instrument, OrderCommand orderCommand) throws JFException {        
        String label = getLabel(instrument);
        double openPriceRounded = roundDecimal(price);
        double openPrice = roundDecimalForCreateOrder(price);
        IOrder order = null;
        
        switch(mode) {
          case 0 :                                                            //NO Stop Loss mode
              if (orderCommand == IEngine.OrderCommand.BUY) {          
                order = engine.submitOrder(label, instrument, orderCommand, amount, 0, slippage, 0, roundDecimal(price + instrument.getPipValue()*takeProfitBuy));          
                printOrder(order, openPriceRounded);
              } else {          
                order = engine.submitOrder(label, instrument, orderCommand, amount, 0, slippage, 0, roundDecimal(price - instrument.getPipValue()*takeProfitSell));
                printOrder(order, openPriceRounded);
              }
            break;
          case 1 :                                                            //Relative mode - counting pipes
              if (orderCommand == IEngine.OrderCommand.BUY) {          
                order = engine.submitOrder(label, instrument, orderCommand, amount, 0, slippage, roundDecimal(price - instrument.getPipValue()*stopLossBuy), roundDecimal(price + instrument.getPipValue()*takeProfitBuy));          
                printOrder(order, openPriceRounded);
              } else {          
                order = engine.submitOrder(label, instrument, orderCommand, amount, 0, slippage, roundDecimal(price + instrument.getPipValue()*stopLossSell), roundDecimal(price - instrument.getPipValue()*takeProfitSell));
                printOrder(order, openPriceRounded);
              }
            break;
          case 2 :                                                            //Absolute mode, stop loss on level
              if (orderCommand == IEngine.OrderCommand.BUY) {          
                order = engine.submitOrder(label, instrument, orderCommand, amount, 0, slippage, stopLossBuy, roundDecimal(price + instrument.getPipValue()*takeProfitBuy));          
                printOrder(order, openPriceRounded);
              } else {          
                order = engine.submitOrder(label, instrument, orderCommand, amount, 0, slippage, stopLossSell, roundDecimal(price - instrument.getPipValue()*takeProfitSell));
                printOrder(order, openPriceRounded);
              }
            break;
        }
        
        order.waitForUpdate(MAX_ORDER_WAIT);      //wait for open
        order.waitForUpdate(MAX_ORDER_WAIT);      //wait for filled
    }
    
    private void printOrder(IOrder order, double price) {
      if (order.getOrderCommand().equals(IEngine.OrderCommand.BUY)) {
        print(order.getState() + " BUY " + order.getLabel() + " price " + roundDecimal(price) + " profit: " + roundDecimal(price + takeProfitPriceBuy));
      } else {
        print(order.getState() + " SELL " + order.getLabel() + " price " + roundDecimal(price) + " profit: " + roundDecimal(price - takeProfitPriceSell));
      }
    }

    protected String getLabel(Instrument instrument) {
        String label = instrument.name();
        label = label + (counter ++);
        label = label.toUpperCase();
        return label;
    }
     /**
     * 
     * @param tickTime market tick time in milliseconds
     * @return
     */
    private boolean isValidTime(long tickTime) {
        
        String formattedTickTime = sdf.format(tickTime); 
        formattedTickTime = formattedTickTime.replace(":", "");
        
        int tickTimeValue = Integer.parseInt(formattedTickTime);
        int startTimeValue = Integer.parseInt(parsedStartTime);
        int endTimeValue = Integer.parseInt(parsedEndTime);
        
        if (startTimeValue < endTimeValue){
            if ((tickTimeValue > startTimeValue) && (tickTimeValue < endTimeValue)){
                return true;
            }
        // Else swap time range and calculate valid time 
        } else {
            int tmpTimeValue = startTimeValue;
            startTimeValue = endTimeValue;
            endTimeValue = tmpTimeValue;
            if ((tickTimeValue < startTimeValue) || (tickTimeValue >= endTimeValue)){
                return true;
            }
        }
        return false;
    }
    private void print(String message) {      
      console.getOut().println(message);
    }
    
    double roundDecimal(double d) {  
      DecimalFormatSymbols format = new DecimalFormatSymbols();
      format.setDecimalSeparator('.');
      DecimalFormat twoDForm = new DecimalFormat("#.####", format);
      String roundedStr = twoDForm.format(d);
      double rounded = Double.valueOf(roundedStr);
      return rounded;
    }
    
    double roundDecimalForCreateOrder(double d) {  
      DecimalFormatSymbols format = new DecimalFormatSymbols();
      format.setDecimalSeparator('.');
      DecimalFormat twoDForm = new DecimalFormat("#.#####", format);
      String roundedStr = twoDForm.format(d);
      double rounded = Double.valueOf(roundedStr);
      return rounded;
    }    
  
  private boolean containsLevel(double price, IEngine.OrderCommand command) throws JFException {    
    List<IOrder> orders = engine.getOrders();    
    double min = price - currencyInstrument.getPipValue()*levelWidth;
    double max = price + currencyInstrument.getPipValue()*levelWidth;
    boolean contains = false;
    for (IOrder order : orders) {
      if (order.getOrderCommand().equals(command))
        if (roundDecimal(order.getOpenPrice()) >= min && roundDecimal(order.getOpenPrice()) <= max || order.getOpenPrice() == 0) {    //Pokud oteviraci hodnota odpovida hladine
          contains = true;
          break;
        }
    }
    return contains;
  }
}
